"""设备详情弹窗。"""

from __future__ import annotations

import uuid
from typing import Dict, Optional

import json

from PySide6.QtCore import Qt, QObject, Slot
from PySide6.QtWidgets import (
    QDialog,
    QFormLayout,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QMessageBox,
    QProgressBar,
    QPushButton,
    QScrollArea,
    QVBoxLayout,
    QWidget,
)

from src.config import load_topic_template_config, load_message_template_config
from src.core.models import Device, DeviceAttribute
from src.services.bool_policy import bool_value_hint, coerce_bool_value, detect_bool_scheme, format_bool_for_display
from src.services.data_generator import AttributeValueGenerator
from src.services.mqtt_client import MQTTService
from src.services.template_validator import MessageTemplateValidator


class DeviceDialog(QDialog):
    """展示设备基本信息及属性测试入口。"""

    def __init__(
        self,
        device: Device,
        generator: AttributeValueGenerator,
        mqtt_service: MQTTService,
        parent=None,
    ) -> None:
        super().__init__(parent)
        self.setWindowTitle(f"{device.nickname} - 属性测试")
        self.resize(900, 600)

        self._device = device
        self._generator = generator
        self._mqtt_service = mqtt_service
        self._attribute_inputs: Dict[str, QLineEdit] = {}
        self._attributes_by_resource: Dict[str, DeviceAttribute] = {}
        
        # 加载Topic和消息模板配置
        self._topic_template = load_topic_template_config().template
        self._message_template = load_message_template_config().template
        # 用于跟踪发布状态
        self._publishing_count = 0
        self._status_label: Optional[QLabel] = None
        self._progress_bar: Optional[QProgressBar] = None
        # 用于跟踪当前对话框发起的发布操作（通过 topic 和 msgid 标识）
        self._pending_publishes: set[str] = set()
        # 跟踪 Signal 是否已连接
        self._signals_connected = False

        self._init_ui()
        self._bind_mqtt_signals()

    # ---------------- UI 构建 ---------------- #

    def _init_ui(self) -> None:
        layout = QGridLayout(self)
        layout.setContentsMargins(12, 12, 12, 12)
        layout.setHorizontalSpacing(12)
        layout.setVerticalSpacing(12)

        info_box = self._build_info_box()
        layout.addWidget(info_box, 0, 0)

        form_widget = self._build_attribute_form()
        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        scroll.setWidget(form_widget)
        layout.addWidget(scroll, 1, 0)

        status_widget = self._build_status_widget()
        layout.addWidget(status_widget, 2, 0)
        
        button_box = self._build_buttons()
        layout.addWidget(button_box, 3, 0, alignment=Qt.AlignmentFlag.AlignRight)

    def _build_info_box(self) -> QGroupBox:
        box = QGroupBox("设备信息")
        form = QFormLayout(box)
        form.setHorizontalSpacing(14)
        form.setVerticalSpacing(8)
        form.setContentsMargins(12, 12, 12, 12)

        for label, value in self._device.summary().items():
            form.addRow(self._label(label), self._value_label(str(value)))

        return box

    def _build_attribute_form(self) -> QWidget:
        container = QWidget()
        layout = QFormLayout(container)
        layout.setHorizontalSpacing(14)
        layout.setVerticalSpacing(10)
        layout.setContentsMargins(6, 10, 6, 10)

        for attr in self._device.attributes:
            # 为属性标签启用换行和ToolTip，确保长key可以完整显示
            label_text = attr.to_form_label()
            label = QLabel(label_text)
            label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop)  # 右对齐，顶部对齐（支持换行）
            label.setWordWrap(True)  # 启用自动换行，支持长文本完整显示
            # 添加ToolTip，显示完整的key信息（包括resource_name等详细信息）
            tooltip_text = f"属性名称：{attr.nickname}\n资源名称：{attr.resource_name}"
            if attr.units:
                tooltip_text += f"\n单位：{attr.units}"
            if attr.description:
                tooltip_text += f"\n描述：{attr.description}"
            label.setToolTip(tooltip_text)
            field_row = QWidget()
            field_layout = QHBoxLayout(field_row)
            field_layout.setContentsMargins(0, 0, 0, 0)
            field_layout.setSpacing(10)

            input_widget = QLineEdit()
            input_widget.setPlaceholderText(f"{attr.value_type} | {attr.point_type}")
            input_widget.setMinimumHeight(28)

            random_button = QPushButton("随机")
            random_button.setMinimumHeight(28)
            publish_button = QPushButton("发布")
            publish_button.setMinimumHeight(28)

            resource_name = attr.resource_name
            self._attribute_inputs[resource_name] = input_widget
            self._attributes_by_resource[resource_name] = attr

            random_button.clicked.connect(lambda _=False, res=resource_name: self._on_random_single(res))
            publish_button.clicked.connect(lambda _=False, res=resource_name: self._on_publish_single(res))

            field_layout.addWidget(input_widget, 1)
            field_layout.addWidget(random_button)
            field_layout.addWidget(publish_button)

            layout.addRow(label, field_row)

        return container

    def _build_status_widget(self) -> QWidget:
        """构建状态显示区域，用于显示发布进度和结果。"""
        container = QWidget()
        layout = QVBoxLayout(container)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(6)
        
        # 状态标签
        self._status_label = QLabel("就绪")
        self._status_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
        self._status_label.setStyleSheet("color: #666; font-size: 12px;")
        self._status_label.setWordWrap(True)  # 启用自动换行，支持多行显示
        
        # 进度条（初始隐藏）
        self._progress_bar = QProgressBar()
        self._progress_bar.setRange(0, 0)  # 不确定进度
        self._progress_bar.setVisible(False)
        self._progress_bar.setMaximumHeight(4)
        
        layout.addWidget(self._status_label)
        layout.addWidget(self._progress_bar)
        
        return container

    def _build_buttons(self) -> QWidget:
        container = QWidget()
        layout = QGridLayout(container)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setHorizontalSpacing(10)
        layout.setVerticalSpacing(0)

        random_button = QPushButton("全部随机")
        submit_button = QPushButton("一键发布")
        close_button = QPushButton("关闭")

        for button in (random_button, submit_button, close_button):
            button.setMinimumHeight(30)

        random_button.clicked.connect(self._on_random)
        submit_button.clicked.connect(self._on_submit)
        close_button.clicked.connect(self.reject)

        layout.addWidget(random_button, 0, 0)
        layout.addWidget(submit_button, 0, 1)
        layout.addWidget(close_button, 0, 2)

        return container

    @staticmethod
    def _label(text: str) -> QLabel:
        label = QLabel(text)
        label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
        return label

    @staticmethod
    def _value_label(text: str) -> QLabel:
        label = QLabel(text)
        label.setTextInteractionFlags(
            Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard
        )
        label.setWordWrap(True)
        return label

    # ---------------- 事件处理 ---------------- #

    def _on_random(self) -> None:
        payload = self._generator.generate_payload(self._device, self._device.attributes)
        for resource, data in payload.items():
            widget = self._attribute_inputs.get(resource)
            attr = self._attributes_by_resource.get(resource)
            if widget and attr:
                widget.setText(self._format_value_for_display(attr, data["value"]))

    def _on_random_single(self, resource_name: str) -> None:
        attr = self._attributes_by_resource.get(resource_name)
        widget = self._attribute_inputs.get(resource_name)
        if not attr or not widget:
            return

        entry = self._generator.generate_entry(attr)
        widget.setText(self._format_value_for_display(attr, entry["value"]))

    def _on_submit(self) -> None:
        """一键发布所有属性（异步）。"""
        try:
            params_dict = self._build_payload_params()
        except ValueError as exc:
            QMessageBox.warning(self, "提示", str(exc))
            return
        except Exception as exc:  # noqa: BLE001
            QMessageBox.critical(self, "发送失败", f"提交过程中出现错误：\n{exc}")
            return
        
        # 构建MQTT消息（使用配置的消息模板）
        msgid = str(uuid.uuid4())
        try:
            message_str = MessageTemplateValidator.format(
                self._message_template,
                msgid=msgid,
                params=params_dict,
            )
            payload = json.loads(message_str)
        except Exception as exc:  # noqa: BLE001
            import logging
            logger = logging.getLogger(__name__)
            logger.error("格式化消息模板失败，使用默认格式：%s", exc)
            # 如果模板格式化失败，使用默认格式
            payload = {
                "msgid": msgid,
                "params": params_dict,
            }
        
        # 使用异步发布（使用配置的Topic模板）
        topic = self._device.topic(self._topic_template)
        publish_key = f"{topic}:{msgid}"
        self._pending_publishes.add(publish_key)
        self._publishing_count = 1
        self._update_publish_status("正在发布...", True)
        self._mqtt_service.publish_async(topic, payload)

    def _on_publish_single(self, resource_name: str) -> None:
        """发布单个属性（异步）。"""
        attr = self._attributes_by_resource.get(resource_name)
        widget = self._attribute_inputs.get(resource_name)
        if not attr or not widget:
            return

        value_text = widget.text().strip()
        if not value_text:
            QMessageBox.warning(self, "提示", f"{attr.nickname} 需要先填写或随机生成数值")
            return

        # 直接读取文本框的值，尝试智能转换但不强制验证
        value = self._convert_value_smart(attr, value_text)

        # 构建参数字典
        params_dict = {
            attr.resource_name: {
                "value": value,
            }
        }
        
        # 构建MQTT消息（使用配置的消息模板）
        msgid = str(uuid.uuid4())
        try:
            message_str = MessageTemplateValidator.format(
                self._message_template,
                msgid=msgid,
                params=params_dict,
            )
            payload = json.loads(message_str)
        except Exception as exc:  # noqa: BLE001
            import logging
            logger = logging.getLogger(__name__)
            logger.error("格式化消息模板失败，使用默认格式：%s", exc)
            # 如果模板格式化失败，使用默认格式
            payload = {
                "msgid": msgid,
                "params": params_dict,
            }

        # 使用异步发布（使用配置的Topic模板）
        topic = self._device.topic(self._topic_template)
        publish_key = f"{topic}:{msgid}"
        self._pending_publishes.add(publish_key)
        self._publishing_count = 1
        self._update_publish_status(f"正在发布 {attr.nickname}...", True)
        self._mqtt_service.publish_async(topic, payload)

    def _build_payload_params(self) -> dict[str, dict[str, object]]:
        """构建参数字典（不包含msgid）。"""
        params: dict[str, dict[str, object]] = {}

        for attr in self._device.attributes:
            widget = self._attribute_inputs.get(attr.resource_name)
            if not widget:
                continue
            value_text = widget.text().strip()
            if not value_text:
                continue
            # 直接读取文本框的值，尝试智能转换但不强制验证
            value = self._convert_value_smart(attr, value_text)
            params[attr.resource_name] = {
                "value": value,
            }

        if not params:
            raise ValueError("请至少填写一个属性值后再发布")

        return params

    def _format_value_for_display(self, attr: DeviceAttribute, value: object) -> str:
        value_type = attr.value_type.lower()
        if value_type in {"bool", "boolean"}:
            scheme = detect_bool_scheme(attr.units)
            return format_bool_for_display(value, scheme)
        return str(value)

    def _convert_value_smart(self, attr: DeviceAttribute, text: str):
        """智能转换文本框的值，尝试转换为合适类型但不强制验证。
        
        如果转换失败，直接返回原始字符串，不抛出异常。
        """
        value_type = (attr.value_type or "").strip().lower()
        text_lower = text.strip().lower()
        
        # 首先检查是否是字符串格式的 true 或 false，自动转换为布尔类型
        if text_lower == "true":
            return True
        if text_lower == "false":
            return False
        
        # 尝试转换为布尔值
        if value_type in {"bool", "boolean"}:
            scheme = detect_bool_scheme(attr.units)
            try:
                return coerce_bool_value(text, scheme)
            except ValueError:
                # 转换失败，返回原始字符串
                return text
        
        # 尝试转换为整数
        if value_type in {"int", "int16", "int32", "int64", "uint16", "uint32", "uint64"}:
            try:
                return int(text)
            except ValueError:
                # 转换失败，返回原始字符串
                return text
        
        # 尝试转换为浮点数
        if value_type in {"float", "double", "float32", "float64"}:
            try:
                return float(text)
            except ValueError:
                # 转换失败，返回原始字符串
                return text
        
        # 其他类型直接返回字符串
        return text

    # ---------------- MQTT Signal 绑定和响应式更新 ---------------- #

    def _bind_mqtt_signals(self) -> None:
        """绑定 MQTT 服务的 Signal，实现响应式 UI 更新。"""
        # 如果已经连接过，先断开
        if self._signals_connected:
            self._unbind_mqtt_signals()
        
        # 使用 Qt.ConnectionType.UniqueConnection 确保每个连接只连接一次
        # 这样可以避免重复连接，即使多次调用也不会重复触发
        self._mqtt_service.publish_started.connect(
            self._on_publish_started, 
            Qt.ConnectionType.UniqueConnection
        )
        self._mqtt_service.publish_succeeded.connect(
            self._on_publish_succeeded,
            Qt.ConnectionType.UniqueConnection
        )
        self._mqtt_service.publish_failed.connect(
            self._on_publish_failed,
            Qt.ConnectionType.UniqueConnection
        )
        self._signals_connected = True
    
    def _unbind_mqtt_signals(self) -> None:
        """断开 MQTT 服务的 Signal 连接。"""
        if not self._signals_connected:
            return
        
        # 使用 suppress_warnings 上下文管理器来抑制警告
        # 或者直接断开，因为我们已经知道连接存在
        # PySide6 的 disconnect 在连接不存在时会发出警告，但不会抛出异常
        # 我们使用一个更安全的方法：先检查信号是否有接收者
        try:
            # 直接断开，如果连接不存在，disconnect 会返回 False 并发出警告
            # 为了完全避免警告，我们需要使用不同的方法
            # 实际上，PySide6 的 disconnect 在连接不存在时会发出警告
            # 但我们可以通过检查信号接收者来避免这个问题
            # 最简单的方法是直接断开，然后忽略警告
            # 或者使用 logging 来抑制警告
            import warnings
            with warnings.catch_warnings():
                warnings.simplefilter("ignore", RuntimeWarning)
                self._mqtt_service.publish_started.disconnect(self._on_publish_started)
                self._mqtt_service.publish_succeeded.disconnect(self._on_publish_succeeded)
                self._mqtt_service.publish_failed.disconnect(self._on_publish_failed)
        except Exception:  # noqa: BLE001
            # 如果断开失败，忽略错误
            pass
        
        self._signals_connected = False

    @Slot(str, dict)
    def _on_publish_started(self, topic: str, payload: dict) -> None:
        """发布开始时更新 UI 状态。"""
        # 只处理当前对话框发起的发布操作
        msgid = payload.get("msgid", "")
        publish_key = f"{topic}:{msgid}"
        if publish_key not in self._pending_publishes:
            return
        # 这个方法在 publish_async 中已经调用了 _update_publish_status
        # 但可以在这里添加额外的逻辑
        pass

    @Slot(str, dict, str)
    def _on_publish_succeeded(self, topic: str, payload: dict, message_id: str) -> None:
        """发布成功时更新 UI 状态。"""
        # 只处理当前对话框发起的发布操作
        msgid = payload.get("msgid", message_id)
        publish_key = f"{topic}:{msgid}"
        if publish_key not in self._pending_publishes:
            return
        
        # 从待处理列表中移除
        self._pending_publishes.discard(publish_key)
        
        self._publishing_count -= 1
        if self._publishing_count <= 0:
            self._publishing_count = 0
            # 在"发布成功！"的上一行显示msgid
            status_message = f"消息ID: {msgid}\n发布成功！"
            self._update_publish_status(status_message, False)
            
            # 判断是单个属性发布还是一键发布
            params_count = len(payload.get("params", {}))
            if params_count == 1:
                # 单个属性发布
                resource_name = list(payload["params"].keys())[0]
                attr = self._attributes_by_resource.get(resource_name)
                if attr:
                    payload_preview = json.dumps(payload, ensure_ascii=False, indent=2)
                    QMessageBox.information(
                        self,
                        "发布成功",
                        f"{attr.nickname} 已单独发布至 MQTT：\n{payload_preview}",
                    )
            else:
                # 一键发布
                QMessageBox.information(
                    self,
                    "发布成功",
                    f"已将所有填写的属性发布至 MQTT 服务器。\n消息 ID: {message_id}",
                )

    @Slot(str, dict, str)
    def _on_publish_failed(self, topic: str, payload: dict, error_message: str) -> None:
        """发布失败时更新 UI 状态。"""
        # 只处理当前对话框发起的发布操作
        msgid = payload.get("msgid", "")
        publish_key = f"{topic}:{msgid}"
        if publish_key not in self._pending_publishes:
            return
        
        # 从待处理列表中移除
        self._pending_publishes.discard(publish_key)
        
        self._publishing_count -= 1
        if self._publishing_count <= 0:
            self._publishing_count = 0
            self._update_publish_status(f"发布失败: {error_message}", False)
            
            QMessageBox.critical(
                self,
                "发送失败",
                f"MQTT 消息发布失败：\n{error_message}\n\n主题: {topic}",
            )

    def _update_publish_status(self, message: str, is_publishing: bool) -> None:
        """更新发布状态显示。"""
        if self._status_label:
            self._status_label.setText(message)
            if is_publishing:
                self._status_label.setStyleSheet("color: #007bff; font-size: 12px; font-weight: bold;")
            else:
                self._status_label.setStyleSheet("color: #28a745; font-size: 12px; font-weight: bold;")
        
        if self._progress_bar:
            self._progress_bar.setVisible(is_publishing)

    def closeEvent(self, event) -> None:  # type: ignore[override]
        """对话框关闭时断开 Signal 连接，避免内存泄漏和重复触发。"""
        # 断开 Signal 连接
        self._unbind_mqtt_signals()
        super().closeEvent(event)

    def _parse_value(self, attr: DeviceAttribute, text: str):
        """保留原有的严格验证方法，供其他可能需要的地方使用。"""
        value_type = attr.value_type.lower()
        if value_type in {"bool", "boolean"}:
            scheme = detect_bool_scheme(attr.units)
            try:
                return coerce_bool_value(text, scheme)
            except ValueError:
                raise ValueError(f"{attr.nickname} 需要填写 {bool_value_hint(scheme)}") from None
        if value_type in {"int", "int16", "int32", "int64", "uint16", "uint32", "uint64"}:
            return int(text)
        if value_type in {"float", "double", "float32", "float64"}:
            return float(text)
        return text


